<?php
class App_Source_Session_Mapper extends Mozg_Mapper_Data
{
	private $_source;

	protected $_entityPath = 'entity/session';

    private static $isStart = false;
    private $_lockId = false;
    private $_maxLifeTime;
    private $_ip;
    private $_httpUserAgent;
    private $_serializedData = '';
    private $_isDataRead = false;
    private $_generateNew = false;
    private $_isUpdate = true;
    private $_sessionExists = false;
    private $_isCheckIp = false;
    private $_isCheckUserAgent = true;

    public function __construct()
    {
        $this->_source = Mozg_Source::get('default');

        $cfg = Mozg_Cfg::Get('global/session');

        $this->_isCheckIp = $cfg->Get('checkIP');
        $this->_isCheckUserAgent = $cfg->Get('checkUserAgent');

        $gc_maxLifeTime = (int)$cfg->Get('maxLifeTime');
        $gc_probability = 1;
        $gc_divisor = 1000;

		@ini_set('session.gc_maxlifetime', $gc_maxLifeTime);
        @ini_set('session.gc_probability', $gc_probability);
        @ini_set('session.gc_divisor', $gc_divisor);
        @ini_set('session.use_only_cookies', "1");
        @ini_set('session.save_handler', 'user');

        session_set_cookie_params($cfg->Get('cookieLifeTime'), "/");

        $this->_maxLifeTime = $gc_maxLifeTime;
        $this->_ip = Mozg::Request('http')->GetClientIP();
        $this->_httpUserAgent = md5($_SERVER["HTTP_USER_AGENT"]);
        $this->_serializedData = '';
        $this->_isDataRead = false;
        $this->_generateNew = false;
        $this->_isUpdate = true;
        $this->_sessionExists = false;
    }

    public function Start($generate_new_id = false)
    {
        if (!self::$isStart)
        {
	        self::$isStart = true;
	        session_name(Mozg_Cfg::Get('global/session')->GetSessionName());
	        // register the new handler
	        session_set_save_handler(array($this , 'Open'), array($this , 'Close'), array($this , 'Read'), array($this , 'Write'), array($this , 'Destroy'), array($this , 'Gc'));

	        session_start();

			if ($_SESSION['session_expire'] && ($_SESSION['session_expire'] - $this->_maxLifeTime <= time()))
			{
	            unset($_SESSION['session_expire']);
	        }
        }

        $this->_generateNew = (bool)$generate_new_id || (bool)$this->_generateNew;
        if ($this->_generateNew)
        {
            $this->RegenerateId();
        }
    }

    public function IsCookie ()
    {
        return isset($_COOKIE[Mozg_Cfg::Get('global/session')->GetSessionName()]);
    }

    public function IsSession ()
    {
        $this->read($_COOKIE[Mozg_Cfg::Get('global/session')->GetSessionName()]);
        return $this->_sessionExists;
    }

    /**
     *  Deletes all data related to the session
     *
     *  @return void
     */
    public function Stop()
    {
        if (! self::$isStart) return;
        setcookie(session_name(), $this->GetId(), time() - 1000, '/');
        session_unset();
        session_destroy();
    }

    /**
     *  Regenerates the session id.
     *
     *  <b>Call this method whenever you do a privilege change!</b>
     *
     *  @return void
     */
    protected function RegenerateId()
    {
        $this->_generateNew = false;
        if (! self::$isStart || ! $this->IsCookie()) return;
        session_regenerate_id(true);
    }

    public function GetId()
    {
        return session_id();
    }

    /**
     *  Custom open() function
     *
     *  @access private
     */
    public function Open ($save_path, $session_name)
    {
        if (! self::$isStart) return true;
        return true;
    }

    /**
     *  Custom close() function
     *
     *  @access private
     */
    public function Close()
    {
        if (! self::$isStart) return true;
        $this->Unlock();
        return true;
    }

    protected function Lock($lockId)
    {
        if (!$lockId)
        {
            $this->_lockId = false;
            return;
        }

        $lockId = (string)$lockId;
        $this->_source->query("SELECT GET_LOCK(" . $this->_source->quote('mozg_sess_' .$lockId) . ", 60)");

        $this->_lockId = $lockId;
    }

    protected function Unlock()
    {
        if (!$this->_lockId) return;

        $stmt = $this->_source->prepare("SELECT RELEASE_LOCK(" . $this->_source->quote('mozg_sess_' .$this->_lockId) . ")");
        $stmt->execute();
    }

    /**
     *  Custom read() function
     *
     *  @access private
     */
    public function Read ($session_id)
    {
        if ($this->_isDataRead) return $this->_serializedData;

        $this->_isDataRead = true;

        // блокируем сессию для параллельного процесса
        $this->Lock($session_id);
        if (! $this->IsCookie())
        {
            $this->_serializedData = '';
            return $this->_serializedData;
        }

        $session_id = (string) $session_id;

        $stmt = $this->_source->query("SELECT
								                session_data,
								                http_user_agent,
								                INET_NTOA(user_ip) user_ip,
								                session_expire
								            FROM
								                %%session_data%%
								            WHERE
								                session_id = " . $this->_source->quote($session_id));

        $fields = $stmt->fetch();

        if (!$fields
        	|| ($this->_isCheckUserAgent && ($fields['http_user_agent'] != $this->_httpUserAgent))
        	|| ($this->_isCheckIp && ($fields['user_ip'] != $this->_ip))
        	|| ($fields['session_expire'] <= (time() - $this->_maxLifeTime)))
        {
            $this->_generateNew = true;
            $this->_serializedData = '';
            return $this->_serializedData;
        }

        $this->_sessionExists = true;
        $this->_serializedData = (string)$fields["session_data"];
        return $this->_serializedData;
    }

    /**
     *  Custom write() function
     *
     *  @access private
     */
    function Write ($session_id, $session_data)
    {
        if (!self::$isStart) return true;

        if ($this->_isUpdate)
        {
	        if ($_SESSION['session_expire'] && ($_SESSION['session_expire'] - $this->_maxLifeTime > time()))
	        {
	            $expire = $_SESSION['session_expire'] - $this->_maxLifeTime;
	        }
	        else
	        {
	            $expire = time();
	        }
        }
        else
        {
        	$expire = false;
        }

        //do not use "query" method, because of bug in some versions of PDO.
        //this is the only working construction here
        $stmt = $this->_source->prepare("SELECT
		        								http_user_agent,
		        								INET_NTOA(user_ip) as user_ip
								            FROM
								                %%session_data%%
								            WHERE
								                session_id = " . $this->_source->quote($session_id));

        $stmt->execute();
        $r = $stmt->fetch();

        //get active user
        $sessionUser = $this->GetEntity('global', 'user');

        if ($r)
        {
            if ((! $this->_isCheckUserAgent || ($r['http_user_agent'] == $this->_httpUserAgent))
            	&& (! $this->_isCheckIp || ($r['user_ip'] == $this->_ip)))
            {
                $this->_source->exec("
		                UPDATE
		                    %%session_data%%
		                SET
		                    session_data = " . $this->_source->quote($session_data) . ",
		                    " .( $expire !== false ?  "session_expire = " .$expire. "," : '' ). "
		                    user_id = " . ($sessionUser->GetIsLogged() ? intval($sessionUser->GetId()) : 'NULL') . ",
		                    user_ip = INET_ATON('" . $this->_ip . "'),
		                    http_user_agent = '" . $this->_httpUserAgent . "'
		                WHERE
			                session_id = " . $this->_source->quote($session_id) . "
		            ");
            }
        }
        else
        {
            $this->_source->exec("
                INSERT INTO
                    %%session_data%%
                        (
                            session_id,
                            http_user_agent,
                            user_ip,
                            " .( $expire !== false ?  "session_expire," : '' ). "
                            session_data,
                            user_id
                        )
                    VALUES
                        (
                            " . $this->_source->quote($session_id) . ",
                            '" . $this->_httpUserAgent . "',
                            INET_ATON('" . $this->_ip . "'),
                            " .( $expire !== false ?  $expire. "," : '' ). "
                            " . $this->_source->quote($session_data) . ",
                            " . ($sessionUser->GetIsLogged() ? intval($sessionUser->GetId()) : 'NULL') . "
                        )
            ");
        }

        $this->Unlock();
    }

    /**
     *  Custom destroy() function
     *
     *  @access private
     */
    public function Destroy ($session_id)
    {
        if (! self::$isStart) return true;

        $this->_source->exec("
            DELETE FROM
                %%session_data%%
            WHERE
                session_id = " . $this->_source->quote($session_id) . "
                " . ($this->_isCheckUserAgent ? " AND http_user_agent = '" . $this->_httpUserAgent . "'" : "") . "
                " . ($this->_isCheckIp ? " AND INET_NTOA(user_ip) = '" . $this->_ip . "'" : ""));

        return true;
    }

    /**
     *  Custom gc() function (garbage collector)
     *
     *  @access private
     */
    public function Gc ($maxLifeTime)
    {
        //if (!self::$isStart) return;
        $this->_source->exec("
            DELETE FROM
                %%session_data%%
            WHERE
                session_expire < " .(time() - $maxLifeTime) . "
        ");
    }

    public function SetExpireDate($seconds = 0)
    {
    	$this->Start();

        if ($seconds <= $this->_maxLifeTime) return;
        $_SESSION['session_expire'] = time() + $seconds;
    }

    public function DoNotUpdate ()
    {
        $this->_isUpdate = false;
    }

    public function GetEntity($scope = '', $area = '')
    {
    	if (!strlen($scope)) $scope = 'global';
    	if (!strlen($area)) $area = 'global';

    	$entityPath = $this->_entityPath. '/' .$scope. '/' .$area;

    	$obj = Mozg_Entity::Get($entityPath, $this->GetData($scope, $area));
    	$obj->Scope($scope);
    	$obj->Area($area);

    	return $obj;
    }

    protected function GetData($scope, $area)
    {
    	$this->Start();

    	if (!isset($_SESSION[$scope]) || !isset($_SESSION[$scope][$area])) return array();
    	return $_SESSION[$scope][$area];
    }

    public function Save(App_Source_Session_Entity $obj)
    {
    	$this->Start();

    	$_SESSION[$obj->Scope()][$obj->Area()] = $obj->ExportData();
    }

    public function Delete(App_Source_Session_Entity $obj)
    {
    	$this->Start();

    	$scope = $obj->Scope();
    	$area = $obj->Area();

    	if (!isset($_SESSION[$scope]) || !isset($_SESSION[$scope][$area])) return true;

    	unset($_SESSION[$scope][$area]);

    	return true;
    }

}